﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Globalization;

namespace System.Windows.Forms;

/// <summary>
///  Base class for the columns in a data grid view.
/// </summary>
[Designer($"System.Windows.Forms.Design.DataGridViewColumnDesigner, {Assemblies.SystemDesign}")]
[DesignTimeVisible(false)]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
[ToolboxItem(false)]
[TypeConverter(typeof(DataGridViewColumnConverter))]
public class DataGridViewColumn : DataGridViewBand, IComponent
{
    private const float DefaultFillWeight = 100F;
    private const int DefaultWidth = 100;
    private const int DefaultMinColumnThickness = 5;

    private const byte AutomaticSort = 0x01;
    private const byte ProgrammaticSort = 0x02;
    private const byte ColumnIsDataBound = 0x04;
    private const byte ColumnIsBrowsableInternal = 0x08;
    private const byte DisplayIndexHasChangedInternal = 0x10;

    private byte _flags;  // see DATAGRIDVIEWCOLUMN_ constants above
    private string _name;
    private int _displayIndex;
    private float _fillWeight, _usedFillWeight;
    private DataGridViewAutoSizeColumnMode _autoSizeMode;
    private string _dataPropertyName = string.Empty;

    // needed for IComponent
    private EventHandler? _disposed;

    private static readonly int s_propDataGridViewColumnValueType = PropertyStore.CreateKey();

    /// <summary>
    ///  Initializes a new instance of the <see cref="DataGridViewColumn"/> class.
    /// </summary>
    public DataGridViewColumn()
        : this(cellTemplate: null)
    {
    }

    public DataGridViewColumn(DataGridViewCell? cellTemplate)
        : base()
    {
        _fillWeight = DefaultFillWeight;
        _usedFillWeight = DefaultFillWeight;
        Thickness = ScaleToCurrentDpi(DefaultWidth);
        MinimumThickness = ScaleToCurrentDpi(DefaultMinColumnThickness);
        _name = string.Empty;
        _displayIndex = -1;
        CellTemplate = cellTemplate;
        _autoSizeMode = DataGridViewAutoSizeColumnMode.NotSet;
    }

    /// <summary>
    ///  Scale to current device dpi settings
    /// </summary>
    /// <param name="value"> initial value</param>
    /// <returns> scaled metric</returns>
    private static int ScaleToCurrentDpi(int value) =>
        ScaleHelper.IsScalingRequirementMet
            ? ScaleHelper.ScaleToDpi(value, ScaleHelper.InitialSystemDpi)
            : value;

    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(DataGridViewAutoSizeColumnMode.NotSet)]
    [SRDescription(nameof(SR.DataGridViewColumn_AutoSizeModeDescr))]
    [RefreshProperties(RefreshProperties.Repaint)]
    public DataGridViewAutoSizeColumnMode AutoSizeMode
    {
        get => _autoSizeMode;
        set
        {
            switch (value)
            {
                case DataGridViewAutoSizeColumnMode.NotSet:
                case DataGridViewAutoSizeColumnMode.None:
                case DataGridViewAutoSizeColumnMode.ColumnHeader:
                case DataGridViewAutoSizeColumnMode.AllCellsExceptHeader:
                case DataGridViewAutoSizeColumnMode.AllCells:
                case DataGridViewAutoSizeColumnMode.DisplayedCellsExceptHeader:
                case DataGridViewAutoSizeColumnMode.DisplayedCells:
                case DataGridViewAutoSizeColumnMode.Fill:
                    break;
                default:
                    throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DataGridViewAutoSizeColumnMode));
            }

            if (_autoSizeMode != value)
            {
                if (Visible && DataGridView is not null)
                {
                    if (!DataGridView.ColumnHeadersVisible &&
                        (value == DataGridViewAutoSizeColumnMode.ColumnHeader ||
                         (value == DataGridViewAutoSizeColumnMode.NotSet && DataGridView.AutoSizeColumnsMode == DataGridViewAutoSizeColumnsMode.ColumnHeader)))
                    {
                        throw new InvalidOperationException(SR.DataGridViewColumn_AutoSizeCriteriaCannotUseInvisibleHeaders);
                    }

                    if (Frozen &&
                        (value == DataGridViewAutoSizeColumnMode.Fill ||
                         (value == DataGridViewAutoSizeColumnMode.NotSet && DataGridView.AutoSizeColumnsMode == DataGridViewAutoSizeColumnsMode.Fill)))
                    {
                        // Cannot set the inherited auto size mode to Fill when the column is frozen
                        throw new InvalidOperationException(SR.DataGridViewColumn_FrozenColumnCannotAutoFill);
                    }
                }

                DataGridViewAutoSizeColumnMode previousInheritedMode = InheritedAutoSizeMode;
                bool previousInheritedModeAutoSized = previousInheritedMode is not DataGridViewAutoSizeColumnMode.Fill
                    and not DataGridViewAutoSizeColumnMode.None
                    and not DataGridViewAutoSizeColumnMode.NotSet;
                _autoSizeMode = value;
                if (DataGridView is null)
                {
                    if (InheritedAutoSizeMode is not DataGridViewAutoSizeColumnMode.Fill
                        and not DataGridViewAutoSizeColumnMode.None
                        and not DataGridViewAutoSizeColumnMode.NotSet)
                    {
                        if (!previousInheritedModeAutoSized)
                        {
                            // Save current column width for later reuse
                            CachedThickness = Thickness;
                        }
                    }
                    else
                    {
                        if (Thickness != CachedThickness && previousInheritedModeAutoSized)
                        {
                            // Restoring cached column width
                            ThicknessInternal = CachedThickness;
                        }
                    }
                }
                else
                {
                    DataGridView.OnAutoSizeColumnModeChanged(this, previousInheritedMode);
                }
            }
        }
    }

    // TypeConverter of the PropertyDescriptor attached to this column
    // in databound cases. Null otherwise.
    internal TypeConverter? BoundColumnConverter { get; set; }

    internal int BoundColumnIndex { get; set; } = -1;

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual DataGridViewCell? CellTemplate { get; set; }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public Type? CellType => CellTemplate?.GetType();

    [DefaultValue(null)]
    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.DataGridView_ColumnContextMenuStripDescr))]
    public override ContextMenuStrip? ContextMenuStrip
    {
        get => base.ContextMenuStrip;
        set => base.ContextMenuStrip = value;
    }

    [Browsable(true)]
    [DefaultValue("")]
    [TypeConverter($"System.Windows.Forms.Design.DataMemberFieldConverter, {Assemblies.SystemDesign}")]
    [Editor($"System.Windows.Forms.Design.DataGridViewColumnDataPropertyNameEditor, {Assemblies.SystemDesign}", typeof(Drawing.Design.UITypeEditor))]
    [SRDescription(nameof(SR.DataGridView_ColumnDataPropertyNameDescr))]
    [SRCategory(nameof(SR.CatData))]
    [AllowNull]
    public string DataPropertyName
    {
        get => _dataPropertyName;
        set
        {
            value ??= string.Empty;

            if (value != _dataPropertyName)
            {
                _dataPropertyName = value;
                DataGridView?.OnColumnDataPropertyNameChanged(this);
            }
        }
    }

    [Browsable(true)]
    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.DataGridView_ColumnDefaultCellStyleDescr))]
    [AllowNull]
    public override DataGridViewCellStyle DefaultCellStyle
    {
        get => base.DefaultCellStyle;
        set => base.DefaultCellStyle = value;
    }

    private bool ShouldSerializeDefaultCellStyle()
    {
        if (!HasDefaultCellStyle)
        {
            return false;
        }

        DataGridViewCellStyle defaultCellStyle = DefaultCellStyle;

        return (!defaultCellStyle.BackColor.IsEmpty ||
                !defaultCellStyle.ForeColor.IsEmpty ||
                !defaultCellStyle.SelectionBackColor.IsEmpty ||
                !defaultCellStyle.SelectionForeColor.IsEmpty ||
                defaultCellStyle.Font is not null ||
                !defaultCellStyle.IsNullValueDefault ||
                !defaultCellStyle.IsDataSourceNullValueDefault ||
                !string.IsNullOrEmpty(defaultCellStyle.Format) ||
                !defaultCellStyle.FormatProvider.Equals(CultureInfo.CurrentCulture) ||
                defaultCellStyle.Alignment != DataGridViewContentAlignment.NotSet ||
                defaultCellStyle.WrapMode != DataGridViewTriState.NotSet ||
                defaultCellStyle.Tag is not null ||
                !defaultCellStyle.Padding.Equals(Padding.Empty));
    }

    internal int DesiredFillWidth { get; set; }

    internal int DesiredMinimumWidth { get; set; }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public int DisplayIndex
    {
        get => _displayIndex;
        set
        {
            if (_displayIndex != value)
            {
                if (value == int.MaxValue)
                {
                    throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.DataGridViewColumn_DisplayIndexTooLarge, int.MaxValue));
                }

                if (DataGridView is not null)
                {
                    if (value < 0)
                    {
                        throw new ArgumentOutOfRangeException(nameof(value), value, SR.DataGridViewColumn_DisplayIndexNegative);
                    }

                    if (value >= DataGridView.Columns.Count)
                    {
                        throw new ArgumentOutOfRangeException(nameof(value), value, SR.DataGridViewColumn_DisplayIndexExceedsColumnCount);
                    }

                    // Will throw an error if a visible frozen column is placed inside a non-frozen area or vice-versa.
                    DataGridView.OnColumnDisplayIndexChanging(this, value);
                    _displayIndex = value;
                    try
                    {
                        DataGridView.InDisplayIndexAdjustments = true;
                        DataGridView.OnColumnDisplayIndexChanged_PreNotification();
                        DataGridView.OnColumnDisplayIndexChanged(this);
                        DataGridView.OnColumnDisplayIndexChanged_PostNotification();
                    }
                    finally
                    {
                        DataGridView.InDisplayIndexAdjustments = false;
                    }
                }
                else
                {
                    if (value < -1)
                    {
                        throw new ArgumentOutOfRangeException(nameof(DisplayIndex), value, SR.DataGridViewColumn_DisplayIndexTooNegative);
                    }

                    _displayIndex = value;
                }
            }
        }
    }

    internal bool DisplayIndexHasChanged
    {
        get => (_flags & DisplayIndexHasChangedInternal) != 0;
        set
        {
            if (value)
            {
                _flags |= DisplayIndexHasChangedInternal;
            }
            else
            {
                _flags = (byte)(_flags & ~DisplayIndexHasChangedInternal);
            }
        }
    }

    internal int DisplayIndexInternal
    {
        set
        {
            Debug.Assert(value >= -1);
            Debug.Assert(value < int.MaxValue);

            _displayIndex = value;
        }
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public event EventHandler? Disposed
    {
        add => _disposed += value;
        remove => _disposed -= value;
    }

    [DefaultValue(0)]
    [SRCategory(nameof(SR.CatLayout))]
    [SRDescription(nameof(SR.DataGridView_ColumnDividerWidthDescr))]
    public int DividerWidth
    {
        get => DividerThickness;
        set => DividerThickness = value;
    }

    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(DefaultFillWeight)]
    [SRDescription(nameof(SR.DataGridViewColumn_FillWeightDescr))]
    public float FillWeight
    {
        get => _fillWeight;
        set
        {
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(value, ushort.MaxValue);

            if (DataGridView is not null)
            {
                DataGridView.OnColumnFillWeightChanging(this, value);
                _fillWeight = value;
                DataGridView.OnColumnFillWeightChanged(this);
            }
            else
            {
                _fillWeight = value;
            }
        }
    }

    internal float FillWeightInternal
    {
        set
        {
            Debug.Assert(value > 0);
            _fillWeight = value;
        }
    }

    [DefaultValue(false)]
    [RefreshProperties(RefreshProperties.All)]
    [SRCategory(nameof(SR.CatLayout))]
    [SRDescription(nameof(SR.DataGridView_ColumnFrozenDescr))]
    public override bool Frozen
    {
        get => base.Frozen;
        set => base.Frozen = value;
    }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [AllowNull]
    public DataGridViewColumnHeaderCell HeaderCell
    {
        get => (DataGridViewColumnHeaderCell)HeaderCellCore;
        set => HeaderCellCore = value;
    }

    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.DataGridView_ColumnHeaderTextDescr))]
    [Localizable(true)]
    [AllowNull]
    public string HeaderText
    {
        get
        {
            if (HasHeaderCell)
            {
                if (HeaderCell.Value is string headerValue)
                {
                    return headerValue;
                }
                else
                {
                    return string.Empty;
                }
            }
            else
            {
                return string.Empty;
            }
        }
        set
        {
            if ((value is not null || HasHeaderCell) &&
                HeaderCell.ValueType is not null &&
                HeaderCell.ValueType.IsAssignableFrom(typeof(string)))
            {
                HeaderCell.Value = value;
            }
        }
    }

    private bool ShouldSerializeHeaderText() => HasHeaderCell && HeaderCell.ContainsLocalValue;

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public DataGridViewAutoSizeColumnMode InheritedAutoSizeMode => GetInheritedAutoSizeMode(DataGridView);

    [Browsable(false)]
    public override DataGridViewCellStyle? InheritedStyle
    {
        get
        {
            DataGridViewCellStyle? columnStyle = null;
            if (HasDefaultCellStyle)
            {
                columnStyle = DefaultCellStyle;
                Debug.Assert(columnStyle is not null);
            }

            if (DataGridView is null)
            {
                return columnStyle;
            }

            DataGridViewCellStyle inheritedCellStyleTmp = new();
            DataGridViewCellStyle dataGridViewStyle = DataGridView.DefaultCellStyle;
            Debug.Assert(dataGridViewStyle is not null);

            if (columnStyle is not null && !columnStyle.BackColor.IsEmpty)
            {
                inheritedCellStyleTmp.BackColor = columnStyle.BackColor;
            }
            else
            {
                inheritedCellStyleTmp.BackColor = dataGridViewStyle.BackColor;
            }

            if (columnStyle is not null && !columnStyle.ForeColor.IsEmpty)
            {
                inheritedCellStyleTmp.ForeColor = columnStyle.ForeColor;
            }
            else
            {
                inheritedCellStyleTmp.ForeColor = dataGridViewStyle.ForeColor;
            }

            if (columnStyle is not null && !columnStyle.SelectionBackColor.IsEmpty)
            {
                inheritedCellStyleTmp.SelectionBackColor = columnStyle.SelectionBackColor;
            }
            else
            {
                inheritedCellStyleTmp.SelectionBackColor = dataGridViewStyle.SelectionBackColor;
            }

            if (columnStyle is not null && !columnStyle.SelectionForeColor.IsEmpty)
            {
                inheritedCellStyleTmp.SelectionForeColor = columnStyle.SelectionForeColor;
            }
            else
            {
                inheritedCellStyleTmp.SelectionForeColor = dataGridViewStyle.SelectionForeColor;
            }

            if (columnStyle is not null && columnStyle.Font is not null)
            {
                inheritedCellStyleTmp.Font = columnStyle.Font;
            }
            else
            {
                inheritedCellStyleTmp.Font = dataGridViewStyle.Font;
            }

            if (columnStyle is not null && !columnStyle.IsNullValueDefault)
            {
                inheritedCellStyleTmp.NullValue = columnStyle.NullValue;
            }
            else
            {
                inheritedCellStyleTmp.NullValue = dataGridViewStyle.NullValue;
            }

            if (columnStyle is not null && !columnStyle.IsDataSourceNullValueDefault)
            {
                inheritedCellStyleTmp.DataSourceNullValue = columnStyle.DataSourceNullValue;
            }
            else
            {
                inheritedCellStyleTmp.DataSourceNullValue = dataGridViewStyle.DataSourceNullValue;
            }

            if (columnStyle is not null && columnStyle.Format.Length != 0)
            {
                inheritedCellStyleTmp.Format = columnStyle.Format;
            }
            else
            {
                inheritedCellStyleTmp.Format = dataGridViewStyle.Format;
            }

            if (columnStyle is not null && !columnStyle.IsFormatProviderDefault)
            {
                inheritedCellStyleTmp.FormatProvider = columnStyle.FormatProvider;
            }
            else
            {
                inheritedCellStyleTmp.FormatProvider = dataGridViewStyle.FormatProvider;
            }

            if (columnStyle is not null && columnStyle.Alignment != DataGridViewContentAlignment.NotSet)
            {
                inheritedCellStyleTmp.AlignmentInternal = columnStyle.Alignment;
            }
            else
            {
                Debug.Assert(dataGridViewStyle.Alignment != DataGridViewContentAlignment.NotSet);
                inheritedCellStyleTmp.AlignmentInternal = dataGridViewStyle.Alignment;
            }

            if (columnStyle is not null && columnStyle.WrapMode != DataGridViewTriState.NotSet)
            {
                inheritedCellStyleTmp.WrapModeInternal = columnStyle.WrapMode;
            }
            else
            {
                Debug.Assert(dataGridViewStyle.WrapMode != DataGridViewTriState.NotSet);
                inheritedCellStyleTmp.WrapModeInternal = dataGridViewStyle.WrapMode;
            }

            if (columnStyle is not null && columnStyle.Tag is not null)
            {
                inheritedCellStyleTmp.Tag = columnStyle.Tag;
            }
            else
            {
                inheritedCellStyleTmp.Tag = dataGridViewStyle.Tag;
            }

            if (columnStyle is not null && columnStyle.Padding != Padding.Empty)
            {
                inheritedCellStyleTmp.PaddingInternal = columnStyle.Padding;
            }
            else
            {
                inheritedCellStyleTmp.PaddingInternal = dataGridViewStyle.Padding;
            }

            return inheritedCellStyleTmp;
        }
    }

    internal bool IsBrowsableInternal
    {
        get => (_flags & ColumnIsBrowsableInternal) != 0;
        set
        {
            if (value)
            {
                _flags |= ColumnIsBrowsableInternal;
            }
            else
            {
                _flags = (byte)(_flags & ~ColumnIsBrowsableInternal);
            }
        }
    }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public bool IsDataBound => IsDataBoundInternal;

    internal bool IsDataBoundInternal
    {
        get => (_flags & ColumnIsDataBound) != 0;
        set
        {
            if (value)
            {
                _flags |= ColumnIsDataBound;
            }
            else
            {
                _flags = (byte)(_flags & ~ColumnIsDataBound);
            }
        }
    }

    [DefaultValue(DefaultMinColumnThickness)]
    [Localizable(true)]
    [SRCategory(nameof(SR.CatLayout))]
    [SRDescription(nameof(SR.DataGridView_ColumnMinimumWidthDescr))]
    [RefreshProperties(RefreshProperties.Repaint)]
    public int MinimumWidth
    {
        get => MinimumThickness;
        set => MinimumThickness = value;
    }

    [Browsable(false)]
    [AllowNull]
    public string Name
    {
        get
        {
            //
            // Change needed to bring the design time and the runtime "Name" property together.
            // The ExtenderProvider adds a "Name" property of its own. It does this for all IComponents.
            // The "Name" property added by the ExtenderProvider interacts only w/ the Site property.
            // The Control class' Name property can be changed only thru the "Name" property provided by the
            // Extender Service.
            //
            // However, the user can change the DataGridView::Name property in the DataGridViewEditColumnDialog.
            // So while the Control can fall back to Site.Name if the user did not explicitly set Control::Name,
            // the DataGridViewColumn should always go first to the Site.Name to retrieve the name.
            //
            // NOTE: one side effect of bringing together the design time and the run time "Name" properties is that
            // DataGridViewColumn::Name changes. However, DataGridView does not fire ColumnNameChanged event.
            // We can't fix this because ISite does not provide Name change notification. So in effect
            // DataGridViewColumn does not know when its name changed.
            // I talked w/ MarkRi and he is perfectly fine w/ DataGridViewColumn::Name changing w/o ColumnNameChanged
            // being fired.
            //
            if (Site is not null && !string.IsNullOrEmpty(Site.Name))
            {
                _name = Site.Name;
            }

            return _name;
        }
        set
        {
            string oldName = _name;
            if (string.IsNullOrEmpty(value))
            {
                _name = string.Empty;
            }
            else
            {
                _name = value;
            }

            if (DataGridView is not null && !string.Equals(_name, oldName, StringComparison.Ordinal))
            {
                DataGridView.OnColumnNameChanged(this);
            }
        }
    }

    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.DataGridView_ColumnReadOnlyDescr))]
    public override bool ReadOnly
    {
        get => base.ReadOnly;
        set
        {
            if (IsDataBound &&
                DataGridView is not null &&
                DataGridView.DataConnection is not null &&
                BoundColumnIndex != -1 &&
                DataGridView.DataConnection.DataFieldIsReadOnly(BoundColumnIndex) &&
                !value)
            {
                throw new InvalidOperationException(SR.DataGridView_ColumnBoundToAReadOnlyFieldMustRemainReadOnly);
            }

            base.ReadOnly = value;
        }
    }

    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.DataGridView_ColumnResizableDescr))]
    public override DataGridViewTriState Resizable
    {
        get => base.Resizable;
        set => base.Resizable = value;
    }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public ISite? Site { get; set; }

    [DefaultValue(DataGridViewColumnSortMode.NotSortable)]
    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.DataGridView_ColumnSortModeDescr))]
    public DataGridViewColumnSortMode SortMode
    {
        get
        {
            if ((_flags & AutomaticSort) != 0x00)
            {
                return DataGridViewColumnSortMode.Automatic;
            }
            else if ((_flags & ProgrammaticSort) != 0x00)
            {
                return DataGridViewColumnSortMode.Programmatic;
            }
            else
            {
                return DataGridViewColumnSortMode.NotSortable;
            }
        }
        set
        {
            if (value != SortMode)
            {
                if (value != DataGridViewColumnSortMode.NotSortable)
                {
                    if (DataGridView is not null &&
                        !DataGridView.InInitialization &&
                        value == DataGridViewColumnSortMode.Automatic &&
                        (DataGridView.SelectionMode == DataGridViewSelectionMode.FullColumnSelect ||
                        DataGridView.SelectionMode == DataGridViewSelectionMode.ColumnHeaderSelect))
                    {
                        throw new InvalidOperationException(string.Format(SR.DataGridViewColumn_SortModeAndSelectionModeClash, (value).ToString(), DataGridView.SelectionMode.ToString()));
                    }

                    if (value == DataGridViewColumnSortMode.Automatic)
                    {
                        _flags = (byte)(_flags & ~ProgrammaticSort);
                        _flags |= AutomaticSort;
                    }
                    else
                    {
                        _flags = (byte)(_flags & ~AutomaticSort);
                        _flags |= ProgrammaticSort;
                    }
                }
                else
                {
                    _flags = (byte)(_flags & ~AutomaticSort);
                    _flags = (byte)(_flags & ~ProgrammaticSort);
                }

                DataGridView?.OnColumnSortModeChanged(this);
            }
        }
    }

    [DefaultValue("")]
    [Localizable(true)]
    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.DataGridView_ColumnToolTipTextDescr))]
    [AllowNull]
    public string ToolTipText
    {
        get => HeaderCell.ToolTipText;
        set
        {
            if (string.Compare(ToolTipText, value, ignoreCase: false, CultureInfo.InvariantCulture) != 0)
            {
                HeaderCell.ToolTipText = value;

                DataGridView?.OnColumnToolTipTextChanged(this);
            }
        }
    }

    internal float UsedFillWeight
    {
        get => _usedFillWeight;
        set
        {
            Debug.Assert(value > 0);
            _usedFillWeight = value;
        }
    }

    [Browsable(false)]
    [DefaultValue(null)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public Type? ValueType
    {
        get => Properties.GetValueOrDefault<Type>(s_propDataGridViewColumnValueType);
        set => Properties.AddOrRemoveValue(s_propDataGridViewColumnValueType, value);
    }

    [DefaultValue(true)]
    [Localizable(true)]
    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.DataGridView_ColumnVisibleDescr))]
    public override bool Visible
    {
        get => base.Visible;
        set => base.Visible = value;
    }

    [SRCategory(nameof(SR.CatLayout))]
    [Localizable(true)]
    [SRDescription(nameof(SR.DataGridView_ColumnWidthDescr))]
    [RefreshProperties(RefreshProperties.Repaint)]
    public int Width
    {
        get => Thickness;
        set => Thickness = value;
    }

    public override object Clone()
    {
        DataGridViewColumn dataGridViewColumn = (DataGridViewColumn)Activator.CreateInstance(GetType())!;
        CloneInternal(dataGridViewColumn);

        return dataGridViewColumn;
    }

    private protected void CloneInternal(DataGridViewColumn dataGridViewColumn)
    {
        base.CloneInternal(dataGridViewColumn);

        dataGridViewColumn._name = Name;
        dataGridViewColumn._displayIndex = -1;
        dataGridViewColumn.HeaderText = HeaderText;
        dataGridViewColumn.DataPropertyName = DataPropertyName;
        dataGridViewColumn.CellTemplate = (DataGridViewCell?)CellTemplate?.Clone();

        if (HasHeaderCell)
        {
            dataGridViewColumn.HeaderCell = (DataGridViewColumnHeaderCell)HeaderCell.Clone();
        }

        dataGridViewColumn.AutoSizeMode = AutoSizeMode;
        dataGridViewColumn.SortMode = SortMode;
        dataGridViewColumn.FillWeightInternal = FillWeight;
    }

    protected override void Dispose(bool disposing)
    {
        try
        {
            if (disposing)
            {
                lock (this)
                {
                    Site?.Container?.Remove(this);
                    _disposed?.Invoke(this, EventArgs.Empty);
                }
            }

            // If you are adding releasing unmanaged resources code here (disposing == false), you need to remove this
            // class type (and all of its subclasses) from check in DataGridViewElement()
            // constructor and DataGridViewElement_Subclasses_SuppressFinalizeCall test!
            // Also consider to modify ~DataGridViewBand() description.
        }
        finally
        {
            base.Dispose(disposing);
        }
    }

    internal DataGridViewAutoSizeColumnMode GetInheritedAutoSizeMode(DataGridView? dataGridView)
    {
        if (dataGridView is not null && _autoSizeMode == DataGridViewAutoSizeColumnMode.NotSet)
        {
            return dataGridView.AutoSizeColumnsMode switch
            {
                DataGridViewAutoSizeColumnsMode.AllCells => DataGridViewAutoSizeColumnMode.AllCells,
                DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader => DataGridViewAutoSizeColumnMode.AllCellsExceptHeader,
                DataGridViewAutoSizeColumnsMode.DisplayedCells => DataGridViewAutoSizeColumnMode.DisplayedCells,
                DataGridViewAutoSizeColumnsMode.DisplayedCellsExceptHeader => DataGridViewAutoSizeColumnMode.DisplayedCellsExceptHeader,
                DataGridViewAutoSizeColumnsMode.ColumnHeader => DataGridViewAutoSizeColumnMode.ColumnHeader,
                DataGridViewAutoSizeColumnsMode.Fill => DataGridViewAutoSizeColumnMode.Fill,
                _ => DataGridViewAutoSizeColumnMode.None,
            };
        }

        return _autoSizeMode;
    }

    public virtual int GetPreferredWidth(DataGridViewAutoSizeColumnMode autoSizeColumnMode, bool fixedHeight)
    {
        if (autoSizeColumnMode is DataGridViewAutoSizeColumnMode.NotSet
            or DataGridViewAutoSizeColumnMode.None
            or DataGridViewAutoSizeColumnMode.Fill)
        {
            throw new ArgumentException(string.Format(SR.DataGridView_NeedColumnAutoSizingCriteria, "autoSizeColumnMode"));
        }

        switch (autoSizeColumnMode)
        {
            case DataGridViewAutoSizeColumnMode.NotSet:
            case DataGridViewAutoSizeColumnMode.None:
            case DataGridViewAutoSizeColumnMode.ColumnHeader:
            case DataGridViewAutoSizeColumnMode.AllCellsExceptHeader:
            case DataGridViewAutoSizeColumnMode.AllCells:
            case DataGridViewAutoSizeColumnMode.DisplayedCellsExceptHeader:
            case DataGridViewAutoSizeColumnMode.DisplayedCells:
            case DataGridViewAutoSizeColumnMode.Fill:
                break;
            default:
                throw new InvalidEnumArgumentException(nameof(autoSizeColumnMode), (int)autoSizeColumnMode, typeof(DataGridViewAutoSizeColumnMode));
        }

        DataGridView? dataGridView = DataGridView;

        Debug.Assert(dataGridView is null || Index > -1);

        if (dataGridView is null)
        {
            return -1;
        }

        DataGridViewAutoSizeColumnCriteriaInternal autoSizeColumnCriteriaInternal = (DataGridViewAutoSizeColumnCriteriaInternal)autoSizeColumnMode;
        Debug.Assert(autoSizeColumnCriteriaInternal is DataGridViewAutoSizeColumnCriteriaInternal.Header
            or DataGridViewAutoSizeColumnCriteriaInternal.AllRows
            or DataGridViewAutoSizeColumnCriteriaInternal.DisplayedRows
            or (DataGridViewAutoSizeColumnCriteriaInternal.Header | DataGridViewAutoSizeColumnCriteriaInternal.AllRows)
            or (DataGridViewAutoSizeColumnCriteriaInternal.Header | DataGridViewAutoSizeColumnCriteriaInternal.DisplayedRows));

        int preferredColumnThickness = 0, preferredCellThickness, rowIndex;
        DataGridViewRow dataGridViewRow;
        Debug.Assert(dataGridView.ColumnHeadersVisible || autoSizeColumnCriteriaInternal != DataGridViewAutoSizeColumnCriteriaInternal.Header);

        // take into account the preferred width of the header cell if displayed and cared about
        if (dataGridView.ColumnHeadersVisible &&
            (autoSizeColumnCriteriaInternal & DataGridViewAutoSizeColumnCriteriaInternal.Header) != 0)
        {
            if (fixedHeight)
            {
                preferredCellThickness = HeaderCell.GetPreferredWidth(-1, dataGridView.ColumnHeadersHeight);
            }
            else
            {
                preferredCellThickness = HeaderCell.GetPreferredSize(-1).Width;
            }

            if (preferredColumnThickness < preferredCellThickness)
            {
                preferredColumnThickness = preferredCellThickness;
            }
        }

        if ((autoSizeColumnCriteriaInternal & DataGridViewAutoSizeColumnCriteriaInternal.AllRows) != 0)
        {
            for (rowIndex = dataGridView.Rows.GetFirstRow(DataGridViewElementStates.Visible);
                rowIndex != -1;
                rowIndex = dataGridView.Rows.GetNextRow(rowIndex, DataGridViewElementStates.Visible))
            {
                dataGridViewRow = dataGridView.Rows.SharedRow(rowIndex);
                if (fixedHeight)
                {
                    preferredCellThickness = dataGridViewRow.Cells[Index].GetPreferredWidth(rowIndex, dataGridViewRow.Thickness);
                }
                else
                {
                    preferredCellThickness = dataGridViewRow.Cells[Index].GetPreferredSize(rowIndex).Width;
                }

                if (preferredColumnThickness < preferredCellThickness)
                {
                    preferredColumnThickness = preferredCellThickness;
                }
            }
        }
        else if ((autoSizeColumnCriteriaInternal & DataGridViewAutoSizeColumnCriteriaInternal.DisplayedRows) != 0)
        {
            int displayHeight = dataGridView.LayoutInfo.Data.Height;
            int cy = 0;

            rowIndex = dataGridView.Rows.GetFirstRow(DataGridViewElementStates.Visible | DataGridViewElementStates.Frozen);
            while (rowIndex != -1 && cy < displayHeight)
            {
                dataGridViewRow = dataGridView.Rows.SharedRow(rowIndex);
                if (fixedHeight)
                {
                    preferredCellThickness = dataGridViewRow.Cells[Index].GetPreferredWidth(rowIndex, dataGridViewRow.Thickness);
                }
                else
                {
                    preferredCellThickness = dataGridViewRow.Cells[Index].GetPreferredSize(rowIndex).Width;
                }

                if (preferredColumnThickness < preferredCellThickness)
                {
                    preferredColumnThickness = preferredCellThickness;
                }

                cy += dataGridViewRow.Thickness;
                rowIndex = dataGridView.Rows.GetNextRow(rowIndex,
                    DataGridViewElementStates.Visible | DataGridViewElementStates.Frozen);
            }

            if (cy < displayHeight)
            {
                rowIndex = dataGridView.DisplayedBandsInfo.FirstDisplayedScrollingRow;
                while (rowIndex != -1 && cy < displayHeight)
                {
                    dataGridViewRow = dataGridView.Rows.SharedRow(rowIndex);
                    if (fixedHeight)
                    {
                        preferredCellThickness = dataGridViewRow.Cells[Index].GetPreferredWidth(rowIndex, dataGridViewRow.Thickness);
                    }
                    else
                    {
                        preferredCellThickness = dataGridViewRow.Cells[Index].GetPreferredSize(rowIndex).Width;
                    }

                    if (preferredColumnThickness < preferredCellThickness)
                    {
                        preferredColumnThickness = preferredCellThickness;
                    }

                    cy += dataGridViewRow.Thickness;
                    rowIndex = dataGridView.Rows.GetNextRow(rowIndex, DataGridViewElementStates.Visible);
                }
            }
        }

        return preferredColumnThickness;
    }

    public override string ToString() =>
        $"DataGridViewColumn {{ Name={Name}, Index={Index} }}";
}
